// Class info from spine-unity.dll
// 
using System;
using UnityEngine;

namespace Spine
{
    public struct BoneMatrix
    {
        public float a, b, c, d, x, y;

        /// <summary>Recursively calculates a worldspace bone matrix based on BoneData.</summary>
        public static BoneMatrix CalculateSetupWorld(BoneData boneData)
        {
            if (boneData == null)
                return default(BoneMatrix);

            // End condition: isRootBone
            if (boneData.Parent == null)
                return GetInheritedInternal(boneData, default(BoneMatrix));

            BoneMatrix result = CalculateSetupWorld(boneData.Parent);
            return GetInheritedInternal(boneData, result);
        }

        static BoneMatrix GetInheritedInternal(BoneData boneData, BoneMatrix parentMatrix)
        {
            BoneData parent = boneData.Parent;
            if (parent == null) return new BoneMatrix(boneData); // isRootBone

            float pa = parentMatrix.a, pb = parentMatrix.b, pc = parentMatrix.c, pd = parentMatrix.d;
            BoneMatrix result = default(BoneMatrix);
            result.x = pa * boneData.X + pb * boneData.Y + parentMatrix.x;
            result.y = pc * boneData.X + pd * boneData.Y + parentMatrix.y;

            switch (boneData.TransformMode)
            {
                case TransformMode.Normal:
                    {
                        float rotationY = boneData.Rotation + 90 + boneData.ShearY;
                        float la = MathUtils.CosDeg(boneData.Rotation + boneData.ShearX) * boneData.ScaleX;
                        float lb = MathUtils.CosDeg(rotationY) * boneData.ScaleY;
                        float lc = MathUtils.SinDeg(boneData.Rotation + boneData.ShearX) * boneData.ScaleX;
                        float ld = MathUtils.SinDeg(rotationY) * boneData.ScaleY;
                        result.a = pa * la + pb * lc;
                        result.b = pa * lb + pb * ld;
                        result.c = pc * la + pd * lc;
                        result.d = pc * lb + pd * ld;
                        break;
                    }
                case TransformMode.OnlyTranslation:
                    {
                        float rotationY = boneData.Rotation + 90 + boneData.ShearY;
                        result.a = MathUtils.CosDeg(boneData.Rotation + boneData.ShearX) * boneData.ScaleX;
                        result.b = MathUtils.CosDeg(rotationY) * boneData.ScaleY;
                        result.c = MathUtils.SinDeg(boneData.Rotation + boneData.ShearX) * boneData.ScaleX;
                        result.d = MathUtils.SinDeg(rotationY) * boneData.ScaleY;
                        break;
                    }
                case TransformMode.NoRotationOrReflection:
                    {
                        float s = pa * pa + pc * pc, prx;
                        if (s > 0.0001f)
                        {
                            s = Math.Abs(pa * pd - pb * pc) / s;
                            pb = pc * s;
                            pd = pa * s;
                            prx = MathUtils.Atan2(pc, pa) * MathUtils.RadDeg;
                        }
                        else
                        {
                            pa = 0;
                            pc = 0;
                            prx = 90 - MathUtils.Atan2(pd, pb) * MathUtils.RadDeg;
                        }
                        float rx = boneData.Rotation + boneData.ShearX - prx;
                        float ry = boneData.Rotation + boneData.ShearY - prx + 90;
                        float la = MathUtils.CosDeg(rx) * boneData.ScaleX;
                        float lb = MathUtils.CosDeg(ry) * boneData.ScaleY;
                        float lc = MathUtils.SinDeg(rx) * boneData.ScaleX;
                        float ld = MathUtils.SinDeg(ry) * boneData.ScaleY;
                        result.a = pa * la - pb * lc;
                        result.b = pa * lb - pb * ld;
                        result.c = pc * la + pd * lc;
                        result.d = pc * lb + pd * ld;
                        break;
                    }
                case TransformMode.NoScale:
                case TransformMode.NoScaleOrReflection:
                    {
                        float cos = MathUtils.CosDeg(boneData.Rotation), sin = MathUtils.SinDeg(boneData.Rotation);
                        float za = pa * cos + pb * sin;
                        float zc = pc * cos + pd * sin;
                        float s = (float)Math.Sqrt(za * za + zc * zc);
                        if (s > 0.00001f)
                            s = 1 / s;
                        za *= s;
                        zc *= s;
                        s = (float)Math.Sqrt(za * za + zc * zc);
                        float r = MathUtils.PI / 2 + MathUtils.Atan2(zc, za);
                        float zb = MathUtils.Cos(r) * s;
                        float zd = MathUtils.Sin(r) * s;
                        float la = MathUtils.CosDeg(boneData.ShearX) * boneData.ScaleX;
                        float lb = MathUtils.CosDeg(90 + boneData.ShearY) * boneData.ScaleY;
                        float lc = MathUtils.SinDeg(boneData.ShearX) * boneData.ScaleX;
                        float ld = MathUtils.SinDeg(90 + boneData.ShearY) * boneData.ScaleY;
                        if (boneData.TransformMode != TransformMode.NoScaleOrReflection ? pa * pd - pb * pc < 0 : false)
                        {
                            zb = -zb;
                            zd = -zd;
                        }
                        result.a = za * la + zb * lc;
                        result.b = za * lb + zb * ld;
                        result.c = zc * la + zd * lc;
                        result.d = zc * lb + zd * ld;
                        break;
                    }
            }

            return result;
        }

        /// <summary>Constructor for a local bone matrix based on Setup Pose BoneData.</summary>
        public BoneMatrix(BoneData boneData)
        {
            float rotationY = boneData.Rotation + 90 + boneData.ShearY;
            float rotationX = boneData.Rotation + boneData.ShearX;

            a = MathUtils.CosDeg(rotationX) * boneData.ScaleX;
            c = MathUtils.SinDeg(rotationX) * boneData.ScaleX;
            b = MathUtils.CosDeg(rotationY) * boneData.ScaleY;
            d = MathUtils.SinDeg(rotationY) * boneData.ScaleY;
            x = boneData.X;
            y = boneData.Y;
        }

        /// <summary>Constructor for a local bone matrix based on a bone instance's current pose.</summary>
        public BoneMatrix(Bone bone)
        {
            float rotationY = bone.Rotation + 90 + bone.ShearY;
            float rotationX = bone.Rotation + bone.ShearX;

            a = MathUtils.CosDeg(rotationX) * bone.ScaleX;
            c = MathUtils.SinDeg(rotationX) * bone.ScaleX;
            b = MathUtils.CosDeg(rotationY) * bone.ScaleY;
            d = MathUtils.SinDeg(rotationY) * bone.ScaleY;
            x = bone.X;
            y = bone.Y;
        }

        public BoneMatrix TransformMatrix(BoneMatrix local)
        {
            return new BoneMatrix
            {
                a = this.a * local.a + this.b * local.c,
                b = this.a * local.b + this.b * local.d,
                c = this.c * local.a + this.d * local.c,
                d = this.c * local.b + this.d * local.d,
                x = this.a * local.x + this.b * local.y + this.x,
                y = this.c * local.x + this.d * local.y + this.y
            };
        }
    }
}
